/**
 * When an index build encounters an error, it signals the primary to abort and waits for an abort
 * oplog entry to be replicated. If a commit entry is received instead, the secondary should crash.
 *
 * @tags: [
 *   requires_fcv_71,
 *   requires_replication,
 *   incompatible_with_windows_tls,
 * ]
 */
import {configureFailPoint} from "jstests/libs/fail_point_util.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {IndexBuildTest} from "jstests/noPassthrough/libs/index_build.js";

// Because this test intentionally crashes the server, we instruct the
// the shell to clean up after us and remove the core dump.
TestData.cleanUpCoreDumpsFromExpectedCrash = true;

const rst = new ReplSetTest({
    nodes: [
        {},
        {
            // Disallow elections on secondary.
            rsConfig: {
                priority: 0,
            },
        },
    ]
});
rst.startSet();
rst.initiate();

const primary = rst.getPrimary();

const testDB = primary.getDB('test');
const coll = testDB.getCollection('test');

const secondary = rst.getSecondary();
const secondaryDB = secondary.getDB(testDB.getName());
const secondaryColl = secondaryDB.getCollection('test');

// Avoid optimization on empty colls.
assert.commandWorked(coll.insert({a: 1}));

// Pause the index builds on the secondary, using the 'hangAfterStartingIndexBuild' failpoint.
const failpointHangAfterInit = configureFailPoint(secondaryDB, "hangAfterInitializingIndexBuild");

// Block the primary before commit.
const failpointHangBeforeCommitPrimary = configureFailPoint(testDB, "hangIndexBuildBeforeCommit");

// Create the index and start the build. Set commitQuorum of 1 node explicitly, we want the primary
// to commit even if the secondary is failing.
const createIdx = IndexBuildTest.startIndexBuild(primary,
                                                 coll.getFullName(),
                                                 {a: 1},
                                                 {},
                                                 [ErrorCodes.InterruptedDueToReplStateChange],
                                                 /*commitQuorum: */ 1);

failpointHangAfterInit.wait();

// Extract the index build UUID. Use assertIndexesSoon to retry until the oplog applier is done with
// the entry, and the index is visible to listIndexes. The failpoint does not ensure this.
const buildUUID =
    IndexBuildTest
        .assertIndexesSoon(secondaryColl, 2, ['_id_'], ['a_1'], {includeBuildUUIDs: true})['a_1']
        .buildUUID;

const failSecondaryBuild =
    configureFailPoint(secondaryDB,
                       "failIndexBuildWithError",
                       {buildUUID: buildUUID, error: ErrorCodes.OutOfDiskSpace});

// Hang while in kAwaitPrimaryAbort, but before signaling the primary.
const failpointHangBeforeSignalingAbort =
    configureFailPoint(secondaryDB, "hangIndexBuildBeforeSignalingPrimaryForAbort");

// Unblock index builds, causing the failIndexBuildWithError failpoint to throw an error.
failpointHangAfterInit.off();

// Unblock primary commit.
failpointHangBeforeCommitPrimary.off();

let res;
assert.soon(function() {
    res = checkProgram(secondary.pid);
    return !res.alive;
});

assert.eq(MongoRunner.EXIT_ABORT, res.exitCode);

// Expect the secondary to crash. Depending on timing, this can be either because the secondary was
// waiting for a primary abort when a 'commitIndexBuild' is applied, or because the build fails and
// tries to request an abort while a 'commitIndexBuild' is being applied.
assert(rawMongoProgramOutput(".*").match('Fatal assertion.*(7329403|7329407)'),
       'Receiving a commit from the primary for a failing index build should crash the secondary');

createIdx();

// Assert index exists on the primary.
IndexBuildTest.assertIndexes(coll, 2, ['_id_', 'a_1']);

TestData.skipCheckDBHashes = true;
rst.stopSet();
